// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ZIRCON_SYSTEM_DEV_AUDIO_GAUSS_TDM_TDM_AUDIO_STREAM_H_
#define ZIRCON_SYSTEM_DEV_AUDIO_GAUSS_TDM_TDM_AUDIO_STREAM_H_

#include <fuchsia/hardware/audio/c/fidl.h>
#include <lib/mmio/mmio.h>
#include <lib/zx/bti.h>
#include <lib/zx/clock.h>
#include <lib/zx/vmo.h>
#include <zircon/listnode.h>

#include <optional>
#include <utility>

#include <audio-proto/audio-proto.h>
#include <ddk/io-buffer.h>
#include <ddk/protocol/i2c.h>
#include <ddk/protocol/platform/device.h>
#include <ddktl/device-internal.h>
#include <ddktl/device.h>
#include <ddktl/protocol/empty-protocol.h>
#include <dispatcher-pool/dispatcher-channel.h>
#include <dispatcher-pool/dispatcher-execution-domain.h>
#include <dispatcher-pool/dispatcher-timer.h>
#include <fbl/mutex.h>
#include <fbl/vector.h>
#include <soc/aml-a113/aml-tdm.h>

namespace audio {
namespace gauss {

class TdmOutputStream;
using TdmAudioStreamBase = ddk::Device<TdmOutputStream, ddk::Messageable, ddk::Unbindable>;

class TdmOutputStream : public TdmAudioStreamBase,
                        public ddk::EmptyProtocol<ZX_PROTOCOL_AUDIO_OUTPUT>,
                        public fbl::RefCounted<TdmOutputStream> {
 public:
  static zx_status_t Create(zx_device_t* parent);

  // void PrintDebugPrefix() const;

  // DDK device implementation
  void DdkUnbind();
  void DdkRelease();
  zx_status_t DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
    return fuchsia_hardware_audio_Device_dispatch(this, txn, msg, &AUDIO_FIDL_THUNKS);
  }

 private:
  static int IrqThread(void* arg);

  friend class fbl::RefPtr<TdmOutputStream>;

  // TODO(hollande) - the fifo bytes are adjustable on the audio fifos and should be scaled
  //                  with the desired sample rate.  Since this first pass has a fixed sample
  //                  sample rate we will set as constant for now.
  //                  We are using fifo C at this stage, which is max of 128 (64-bit wide)
  //                  Using 64 levels for now.
  static constexpr uint8_t kFifoDepth = 0x40;

  TdmOutputStream(zx_device_t* parent, fbl::RefPtr<dispatcher::ExecutionDomain>&& default_domain)
      : TdmAudioStreamBase(parent),
        default_domain_(std::move(default_domain)),
        create_time_(zx::clock::get_monotonic().get()) {}

  virtual ~TdmOutputStream();

  // Device FIDL implementation
  zx_status_t GetChannel(fidl_txn_t* txn);

  zx_status_t Bind(const char* devname);

  void ReleaseRingBufferLocked() __TA_REQUIRES(lock_);

  zx_status_t AddFormats(fbl::Vector<audio_stream_format_range_t>* supported_formats);

  // Thunks for dispatching stream channel events.
  zx_status_t ProcessStreamChannel(dispatcher::Channel* channel, bool privileged);
  void DeactivateStreamChannel(const dispatcher::Channel* channel);

  zx_status_t OnGetStreamFormatsLocked(dispatcher::Channel* channel,
                                       const audio_proto::StreamGetFmtsReq& req) const
      __TA_REQUIRES(lock_);
  zx_status_t OnSetStreamFormatLocked(dispatcher::Channel* channel,
                                      const audio_proto::StreamSetFmtReq& req, bool privileged)
      __TA_REQUIRES(lock_);
  zx_status_t OnGetGainLocked(dispatcher::Channel* channel,
                              const audio_proto::GetGainReq& req) const __TA_REQUIRES(lock_);
  zx_status_t OnSetGainLocked(dispatcher::Channel* channel, const audio_proto::SetGainReq& req)
      __TA_REQUIRES(lock_);
  zx_status_t OnPlugDetectLocked(dispatcher::Channel* channel,
                                 const audio_proto::PlugDetectReq& req) __TA_REQUIRES(lock_);
  zx_status_t OnGetUniqueIdLocked(dispatcher::Channel* channel,
                                  const audio_proto::GetUniqueIdReq& req) const
      __TA_REQUIRES(lock_);
  zx_status_t OnGetStringLocked(dispatcher::Channel* channel,
                                const audio_proto::GetStringReq& req) const __TA_REQUIRES(lock_);

  // Thunks for dispatching ring buffer channel events.
  zx_status_t ProcessRingBufferChannel(dispatcher::Channel* channel);

  void DeactivateRingBufferChannel(const dispatcher::Channel* channel);

  zx_status_t SetModuleClocks();

  zx_status_t ProcessRingNotification();

  // Stream command handlers
  // Ring buffer command handlers
  zx_status_t OnGetFifoDepthLocked(dispatcher::Channel* channel,
                                   const audio_proto::RingBufGetFifoDepthReq& req) const
      __TA_REQUIRES(lock_);
  zx_status_t OnGetBufferLocked(dispatcher::Channel* channel,
                                const audio_proto::RingBufGetBufferReq& req) __TA_REQUIRES(lock_);
  zx_status_t OnStartLocked(dispatcher::Channel* channel, const audio_proto::RingBufStartReq& req)
      __TA_REQUIRES(lock_);
  zx_status_t OnStopLocked(dispatcher::Channel* channel, const audio_proto::RingBufStopReq& req)
      __TA_REQUIRES(lock_);

  static fuchsia_hardware_audio_Device_ops_t AUDIO_FIDL_THUNKS;

  fbl::Mutex lock_;
  fbl::Mutex req_lock_ __TA_ACQUIRED_AFTER(lock_);

  // Dispatcher framework state
  fbl::RefPtr<dispatcher::Channel> stream_channel_ __TA_GUARDED(lock_);
  fbl::RefPtr<dispatcher::Channel> rb_channel_ __TA_GUARDED(lock_);
  fbl::RefPtr<dispatcher::ExecutionDomain> default_domain_;

  // control registers for the tdm block
  std::optional<ddk::MmioBuffer> mmio_;

  fbl::RefPtr<dispatcher::Timer> notify_timer_;

  // TODO(johngro) : support parsing and selecting from all of the format
  // descriptors present for a stream, not just a single format (with multiple
  // sample rates).
  fbl::Vector<audio_stream_format_range_t> supported_formats_;

  pdev_protocol_t pdev_;
  i2c_protocol_t i2c_;

  fbl::unique_ptr<Tas57xx> left_sub_;
  fbl::unique_ptr<Tas57xx> right_sub_;
  fbl::unique_ptr<Tas57xx> tweeters_;

  float current_gain_ = -20.0;

  uint32_t frame_size_;
  uint32_t fifo_bytes_;

  const zx_time_t create_time_;
  uint32_t us_per_notification_ = 0;
  volatile bool running_;

  zx::bti bti_;
  io_buffer_t ring_buffer_;
  void* ring_buffer_virt_ = nullptr;
  uint32_t ring_buffer_phys_ = 0;
  uint32_t ring_buffer_size_ = 0;
};

}  // namespace gauss
}  // namespace audio

#endif  // ZIRCON_SYSTEM_DEV_AUDIO_GAUSS_TDM_TDM_AUDIO_STREAM_H_
